/******************************************************************************
 cnome: A C++ source "genes" discover. :-)

 See more details at:

   http://www.codeplex.com/cnome

 The original author site is at: http://www.alemosbarbosa.com/

 Copyright (c) 2008-2010 Alexandre Lemos Barbosa.
 Distributed under the MIT License. (See accompanying file LICENSE-MIT.txt 
 or at http://www.codeplex.com/cnome/license)
 ******************************************************************************/

#include "cnome_config.h"

#include <string>

#include "scopes.h"

#include "iterator.h"
#include "symbol.h"
#include "util.h"
#include "variables.h"

using namespace std;
using namespace cnome::util;

namespace cnome { namespace scopes {
bool is_builtin(const string& v) {
	string::size_type prevIdx = v.find_first_not_of(' ');
	string::size_type idx;
	while(prevIdx != string::npos) {
		idx = v.find(' ', prevIdx);
		if (idx != prevIdx) {
			string aux(v.substr(prevIdx, idx != string::npos ? idx - prevIdx : string::npos));
			if (aux != "const") {
				itSymbolItem it = cnome_symbols.find(aux);
				if (it == cnome_symbols.end()) {
					return false;
				}
				else if (it->second->get_symbol_type() != symbol::keyword) {
					return false;
				}
			}
			if (idx == string::npos) {
				break;
			}
		}
		prevIdx = v.find_first_not_of(' ', idx);
	} 

	return true;
}

// TODO: Handle builtin and method parameters properly.
// TODO: Handle pointer to function properly. Ex: void (*)(void)
// TODO: Handle val == ""
scopesFromStr::scopesFromStr(const string& v, size_t suffixSize):m_first_time(true), m_idx(0), m_value(v),
        m_suffixSize(suffixSize), m_idxLstItSbls(var::l_usgDrts.begin()) {
    if (m_suffixSize == string::npos) {
        m_suffixSize = var::scopeSuffixSize;
    } else if (m_suffixSize == 0 || m_suffixSize > m_value.size()) {
        m_suffixSize = m_value.size();
    }
}

const string& scopesFromStr::current_scope()const{ return m_scope; }

size_t scopesFromStr::find_right_char(size_t leftIdx, char leftChar, char rightChar) {
    size_t auxRPrtIdx = leftIdx;
    int inside = 1;
    for (auxRPrtIdx = leftIdx+1; auxRPrtIdx < m_value.size(); auxRPrtIdx++) {
        if (m_value[auxRPrtIdx] == rightChar)
            inside--;
        else if (m_value[auxRPrtIdx] == leftChar)
            inside++;
        if (!inside)
            break;
    }
    return !inside?auxRPrtIdx:string::npos;
}

size_t scopesFromStr::find_right_parenthesis(size_t leftIdx) {
    return find_right_char(leftIdx, '(', ')');
}

size_t scopesFromStr::find_greater(size_t leftIdx) {
    return find_right_char(leftIdx, '<', '>');
}

void scopesFromStr::advance_idx() {
    size_t idx2 = m_idx;
    size_t idx3;
    size_t idxLP;
    size_t idxRP;
    size_t idxLess;
    size_t idxGreater;
    do {
        if (idx2 != string::npos && m_value.size() > 1 && idx2 < m_value.size()-2)
            idx3 = m_value.find_first_of(":$", idx2+(m_value[idx2]==':'&&m_value[idx2+1]==':'?2:1));
        else {
            m_idx = string::npos;
            return;
        }

        // Verify if idx3 is inside a parameter list
        for (idxLess = idxLP = idx2, idxGreater = idxRP = m_idx;    idxLP != string::npos && idxLP + 1 < m_value.size() 
            && idxRP != string::npos && idxRP+1 < m_value.size() 
            && idxLP < idx3;) {
            idxLP = m_value.find('(', idxLP+1);
            while (     idxLP != string::npos              // Verify if it is a pointer to function
                && (    m_value.substr(idxLP, 3) == "(*)"       
                || m_value.substr(idxLP, 8) == "(*const)" 
                )    
                )
                idxLP = m_value.find('(', idxLP + 1);        // Skip it
            idxRP = find_right_parenthesis(idxLP);
            idxLess = m_value.find('<', idxLess + 1);
            idxGreater = find_greater(idxLess);
            while (    idxLP != string::npos && idxRP != string::npos  && idx3 != string::npos
                && ((idxLP <= idx3 && idx3 <= idxRP) || (idxLess <= idx3 && idx3 <= idxGreater)))
                idx3 = m_value.find_first_of(":$", idx3+(m_value[idx3]==':'&&m_value[idx3+1]==':'?2:1));
        } 
        idx2 = idx3;
    }while (    idx2 != string::npos && idxLP != string::npos && idxRP != string::npos 
        && (idxLP <= idx2 && idx2 <= idxRP) );
    m_idx = idx2;
    if (m_idx > m_value.size() - m_suffixSize) // Is index inside suffix?
        m_idx = string::npos;
}

const string& scopesFromStr::next_scope() {
    if (m_idx != m_value.npos || m_first_time) {
        if (m_idx == m_value.npos) {
            m_scope = m_value;
            m_first_time = false;
        }
        else {
            size_t incIdx = m_value[m_idx]==':'?2:m_value[m_idx]=='$'?1:0;
            if (m_idx+incIdx <= m_value.size()-m_suffixSize)
                m_scope = m_value.substr(0,m_idx+incIdx)+m_value.substr(m_value.size()-m_suffixSize);
            else
                m_scope = m_value.substr(m_value.size()-m_suffixSize);
            advance_idx();
            if (m_idx == string::npos)
                m_first_time = false;
        }
        if (m_scope.size()&&!is_builtin(m_scope)&&m_scope!="...") {
            if (m_scope[0] == '$') {
                if (m_scope.size() > 1)
                    m_scope = m_scope.substr(1);
                else
                    m_scope = "";
            }
            if (m_scope[0] != ':')
                m_scope = "::" + m_scope;
        }
    }
    else if (m_idxLstItSbls != var::l_usgDrts.end()) {
        m_scope = (*m_idxLstItSbls)->second->to_str() + ( getSuffixForSymbol(*(*m_idxLstItSbls)->second) + m_value.substr(m_value.size() - m_suffixSize));
        ++m_idxLstItSbls;
    }
    else m_scope = "";
    
    return m_scope;
}

scopesFromScp::scopesFromScp(const symbolPtr& v, size_t suffixSize )
: m_suffixSize(suffixSize), m_sbl(v), m_value(m_sbl?m_sbl->to_str():"")
{}

const std::string& scopesFromScp::currentScope()const {
    return m_value;
}

const std::string& scopesFromScp::nextScope() {
    if (m_sbl) {
        if (strlen(m_sbl->to_str()) <= m_suffixSize)
            m_value = "";
        else {
            const_cast<symbolPtr&>(m_sbl) = m_sbl->get_scope();
            m_value = m_sbl?m_sbl->to_str():"";
        }
    }

    return m_value;
}

inline std::string& get_scope(scopeT& sbl, std::string& scp, bool restart = true) {
    if (sbl.size()) {
        if (sbl.back()->get_symbol_type() == symbol::keyword)
            scp = sbl.back()->to_str();
        else
            scp += sbl.back()->to_str();
        appendAdorners(scp, sbl.back()->get_adorners());
    }
    return scp;
}

void adjust_scope(std::string& scp, const char* tk) {
	const char* colon_colon = tk;
	const char* dot = tk;
	const char* ptr_deference = tk;
	const char* cur = tk;
	const char* qId = tk;
	std::string sub = "";
	int idx = -1;
	while ((colon_colon = strstr(cur, "::")) || (dot = strstr(cur, ".")) || (ptr_deference = strstr(cur, "->"))) {
		qId = std::max(qId, colon_colon);
		qId = std::max(qId, dot);
		qId = std::max(qId, ptr_deference);
		std::string sub2(tk,qId);
		sub2 = "::" + sub2;
		int idx2 = scp.rfind(sub2);
		if (idx2 < 0) { 
			break;
		}
		else if (idx2 + sub2.size() < scp.size()) {
			if (idx2 + sub2.size() + 2 > scp.size()) {
				break;
			}
			else if (scp.substr(idx2+sub2.size(),2) == "::") {
				sub2 += "::";
			}
			else {
				break;
			}
		}
		sub = sub2;
		idx = idx2;
		cur = qId + 1;
	}
	if (idx >= 0 && sub.size() > 0) {
		scp = scp.substr(0, idx);
	}
}

std::string& get_scope(std::string& scp, const char* tk) {
    itSymbolItem it;
    if (    tk 
         && (it = cnome_symbols.find(tk)) != cnome_symbols.end() 
         && it->second->get_symbol_type() == symbol::keyword
         && *(it->second) != boost::wave::T_ELLIPSIS
       )
           return scp = tk;

    get_scope(var::scope, scp);
    if (tk) {
		adjust_scope(scp, tk);
        if (isCtorDeclWithoutParent(scp)) // TODO: Verify why enterscope for a::a isn't recognized the second a as a constructor
            scp = scp + '$' + tk;
        else if (var::scope.size())
            scp = scp + getSuffixForSymbol(*var::scope.back()) + tk;
        else 
            scp = scp + getSuffixForSymbol(smb_FIRST_TOKEN) + tk;
    }
    return scp;
}

// TODO: Study the use of useCurScope parameter bellow. 
std::string& get_scope(std::string& scp, scopeT& tk, bool useCurScope) {
    if (useCurScope)
        get_scope(scp);
    get_scope(tk, scp, false);

    return scp;
}

std::string& get_scope(std::string& scp, scopeT& tk, size_t& suffixSize, bool useCurScope) {
    size_t auxSz = 0;
    if (useCurScope) {
        get_scope(scp);
        auxSz = scp.size();
    }
    get_scope(tk, scp, false);
    
    itSymbolItem it;
    if (    (it = cnome_symbols.find(scp)) != cnome_symbols.end() 
         && it->second->get_symbol_type() == symbol::keyword
       )
           auxSz=0;
    suffixSize = (scp.size() >= auxSz)?scp.size()-auxSz:0;

    return scp;
}

std::string& get_previous_scope(std::string& scp) {
    if (!var::l_scope.size())
        return scp;  // No previous scope

    return get_scope(var::l_scope.back(), scp, false);
}

// TODO: Describe and find a better name to method bellow.
void strToScope(const string curScp, const string& val, string& auxScope, iterator& first) {
    string auxPrefix = curScp.substr(0, curScp.rfind(val));
    int insideParameters = 0;
    for (std::size_t idx = 0; idx < val.size(); idx++) {
        if (val[idx] == '(' || val[idx] == '<') {
            insideParameters++;
            continue;
        }
        else if (val[idx] == ')' || val[idx] == '>') {
            insideParameters--;
            continue;
        }
        else if ((val[idx] == ':' || val[idx] == '$') && insideParameters == 0) {
            auxScope = val.substr(0,idx);
            auxScope = auxPrefix + auxScope;
            itSymbolItem it;
            if ((it=cnome_symbols.find(auxScope)) != cnome_symbols.end())
                var::scope.push_back(it->second);
            else {
                token_t tk = *first;
                tk.set_value(auxScope.c_str());
                symbolPtr tmp = symbolPtr(new symbol(tk, symbol::other, first.get_cnome_position()));
                var::scope.push_back(tmp);
            }
            if (val[idx] == ':' && val[idx+1] == ':')
                idx++;
        }
    }
    auxScope = val;
    auxScope = auxPrefix + auxScope;
}

const itSymbolItem get_symbol_scope(const char* scp) {
    if (!scp || !*scp)  // Is scp a valid pointer or empty?
        return cnome_symbols.end();

    int idx = strlen(scp) - 1;
    if (idx > 4 && !strcmp(scp+idx-4, "const")) // Handle special case "int a::ab(void)const"
        idx-=5;
    if ( scp[idx] == ')') {
        int parentMatch;
        for (parentMatch = 1, idx--; parentMatch && idx >= 0; idx--)
            if (scp[idx] == '(')
                parentMatch--;
            else if (scp[idx] == ')')
                parentMatch++;
    }
    for(;idx>0&&scp[idx-1]!=':'&&scp[idx-1]!='$';idx--);
    for(;idx>0&&(scp[idx-1]==':'||scp[idx-1]=='$');idx--);
    if (!idx)
        return cnome_symbols.end();
    else {
        itSymbolItem ret;
        char tmp = scp[idx];
        const_cast<char*>(scp)[idx] = '\0';
        ret = cnome_symbols.find(scp);
        const_cast<char*>(scp)[idx]=tmp;
        return ret;
    }
}

void enter_scope(std::string& val, iterator& first) {
#if defined(CNOME_DEBUG)
    string prevScp;
    get_scope(prevScp);
#endif
    string oldScp;
    get_scope(oldScp,val.c_str());

    std::string auxPrefix = oldScp.substr(0,oldScp.rfind(val));

    string auxScope;
    strToScope(oldScp, val, auxScope, first);

    itSymbolItem it = cnome_symbols.end();
    scopesFromStr scopes(auxScope, val.size());
    while (scopes.next_scope().size()) {
        itSymbolItem it2;
        if ((it2=cnome_symbols.find(scopes.current_scope())) != cnome_symbols.end())
            it = it2;
    }
    symbolPtr smb;
    if (it != cnome_symbols.end())
        smb = it->second;
    else {
        token_t tk = *first;
        tk.set_value(val.c_str());
        smb = symbolPtr(new symbol(tk, symbol::other, first.get_cnome_position()));
        itSymbolItem it;
        if ((it=get_symbol_scope(oldScp.c_str())) != cnome_symbols.end())
            smb->set_scope(it->second);
        else if (var::scope.size())
            smb->set_scope(var::scope.back());
    }
    var::scope.push_back(smb);

#if defined(CNOME_DEBUG)
    string newScp;
    get_scope(newScp);
    std::cout << "enter_scope: val = " << val << endl;
    std::cout << "enter_scope: " << newScp << " from " << prevScp << endl;
#endif
}
}} // namespace cnome { namespace scopes {
